use crate::ast::*;
use crate::error::{CompilerError, Result};
use crate::llvm_backend::LLVMBackend;
use inkwell::types::BasicTypeEnum;
use inkwell::values::BasicValueEnum;
use inkwell::AddressSpace;

impl<'ctx> LLVMBackend<'ctx> {

    /// Generate Option constructor (Option::Some(value) or Option::None)
    ///
    /// Option<T> is represented as: { i32 tag, [N x i8] data }
    /// - tag = 0: Some(T)
    /// - tag = 1: None
    pub(in crate::llvm_backend) fn generate_option_constructor(
        &mut self,
        variant_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        match variant_name {
            "Some" => {
                if arguments.len() != 1 {
                    return Err(CompilerError::internal(&format!(
                        "Option::Some() expects 1 argument, got {}",
                        arguments.len()
                    )));
                }

                // Generate the value
                let value = self.generate_expression(&arguments[0])?;

                // Infer Option type from the argument
                let value_type = self.infer_expression_type(&arguments[0]).ok_or_else(|| {
                    CompilerError::internal("Could not infer Option::Some value type")
                })?;
                let option_type = crate::ast::types::OptionType {
                    some_type: Box::new(value_type),
                    location: arguments[0].location(),
                };

                // Create Option struct: { i32 tag=0, [N x i8] data }
                self.create_option_value(0, Some(value), &option_type)
            }
            "None" => {
                // For None, we need to infer the type from context
                // This should be handled by type inference
                // For now, we'll create a None with int type as placeholder
                if !arguments.is_empty() {
                    return Err(CompilerError::internal(&format!(
                        "Option::None expects 0 arguments, got {}",
                        arguments.len()
                    )));
                }

                // We need the Option type from the context
                // This is a bit tricky - we'll use int type as placeholder
                // TODO: Get actual Option type from variable declaration context
                let option_type = crate::ast::types::OptionType {
                    some_type: Box::new(Type::Basic(crate::ast::types::BasicType::Int)),
                    location: crate::error::Location::new(0, 0, 0),
                };

                // Create Option struct with tag=1 (None)
                self.create_option_value(1, None, &option_type)
            }
            _ => Err(CompilerError::internal(&format!(
                "Unknown Option variant: {}",
                variant_name
            ))),
        }
    }


    /// Create an Option value with given tag and optional data
    pub(in crate::llvm_backend) fn create_option_value(
        &mut self,
        tag: u32,
        value: Option<BasicValueEnum<'ctx>>,
        option_type: &crate::ast::types::OptionType,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Get the LLVM type for Option<T>
        let option_llvm_type = match self.get_option_llvm_type(option_type)? {
            BasicTypeEnum::StructType(st) => st,
            _ => return Err(CompilerError::internal("Option type must be a struct")),
        };

        // Create a constant zero-initialized struct
        let zero_init = option_llvm_type.const_zero();

        // Set the tag field
        let tag_value = self.context.i32_type().const_int(tag as u64, false);
        let with_tag = self
            .builder
            .build_insert_value(zero_init, tag_value, 0, "option_with_tag")?
            .into_struct_value();

        // If this is Some variant, store the value in the data field
        if let Some(val) = value {
            // Allocate temporary storage for the value
            let option_ptr = self.builder.build_alloca(option_llvm_type, "option_temp")?;
            self.builder.build_store(option_ptr, with_tag)?;

            // Get pointer to data field using build_gep
            let data_array_ptr = unsafe {
                self.builder.build_gep(
                    option_llvm_type,
                    option_ptr,
                    &[
                        self.context.i32_type().const_zero(),
                        self.context.i32_type().const_int(1, false),
                    ],
                    "option_data_array_ptr",
                )?
            };

            // CRITICAL: data_array_ptr is [N x i8]*, we need to GEP to element 0!
            // This gets us i8* instead of [N x i8]*
            let data_first_byte_ptr = unsafe {
                self.builder.build_gep(
                    self.context.i8_type().array_type(8), // Use fixed size for now
                    data_array_ptr,
                    &[
                        self.context.i32_type().const_zero(),
                        self.context.i32_type().const_zero(),
                    ],
                    "data_first_byte",
                )?
            };

            // Get the Some type
            let some_type = self.nrc_type_to_llvm_type(&Some(*option_type.some_type.clone()))?;

            // Now cast i8* to the target type pointer
            let some_ptr_type = match some_type {
                BasicTypeEnum::IntType(t) => t.ptr_type(AddressSpace::default()),
                BasicTypeEnum::FloatType(t) => t.ptr_type(AddressSpace::default()),
                BasicTypeEnum::PointerType(t) => t,
                BasicTypeEnum::StructType(t) => t.ptr_type(AddressSpace::default()),
                BasicTypeEnum::ArrayType(t) => t.ptr_type(AddressSpace::default()),
                BasicTypeEnum::VectorType(t) => t.ptr_type(AddressSpace::default()),
            };
            let typed_data_ptr = self.builder.build_pointer_cast(
                data_first_byte_ptr,
                some_ptr_type,
                "typed_data_ptr",
            )?;

            // Store the value directly
            self.builder.build_store(typed_data_ptr, val)?;

            // Load the complete Option value
            let result = self
                .builder
                .build_load(option_llvm_type, option_ptr, "option_result")?;
            Ok(result)
        } else {
            // For None, just return the struct with tag set
            Ok(with_tag.into())
        }
    }

}
